{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function\n",
    "import sdm as sdmlib\n",
    "from collections import defaultdict\n",
    "import random\n",
    "import string\n",
    "import time\n",
    "from math import ceil\n",
    "from IPython.display import clear_output\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "#%load_ext line_profiler\n",
    "\n",
    "empty = ' '\n",
    "flip_table = string.maketrans('OX', 'XO')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        self.stats = defaultdict(int)\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def on_finish(self, winner, seq):\n",
    "        pass\n",
    "    \n",
    "    def next_move(self, step, board):\n",
    "        v = []\n",
    "        for idx, x in enumerate(board):\n",
    "            if x == empty:\n",
    "                v.append(idx)\n",
    "        return random.choice(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SmartPlayer(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        self.stats = defaultdict(int)\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def on_finish(self, winner, seq):\n",
    "        pass\n",
    "    \n",
    "    def next_move(self, step, board):\n",
    "        v = []\n",
    "        v_block = []\n",
    "        #print('='*20)\n",
    "        #print('board', board)\n",
    "        for idx, x in enumerate(board):\n",
    "            if x == empty:\n",
    "                boardX = board[:idx] + 'X' + board[idx+1:]\n",
    "                boardO = board[:idx] + 'O' + board[idx+1:]\n",
    "                winnerX = check_for_winner(boardX)\n",
    "                winnerO = check_for_winner(boardO)\n",
    "                #print('Name', self.name)\n",
    "                #print_board(boardX)\n",
    "                #print('WinnerX', winnerX)\n",
    "                #print_board(boardO)\n",
    "                #print('WinnerO', winnerO)\n",
    "                #print('')\n",
    "                if self.name == winnerX or self.name == winnerO:\n",
    "                    # Wins the game.\n",
    "                    return idx\n",
    "                elif winnerX is not None or winnerO is not None:\n",
    "                    # Prevents a defeat.\n",
    "                    v_block.append(idx)\n",
    "                v.append(idx)\n",
    "        #print('='*20)\n",
    "        if v_block:\n",
    "            return v_block[0]\n",
    "        return random.choice(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class HumanPlayer(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        self.stats = defaultdict(int)\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def on_finish(self, winner, seq):\n",
    "        pass\n",
    "    \n",
    "    def next_move(self, step, board):\n",
    "        #clear_output(wait=True)\n",
    "        while True:\n",
    "            print_board(board)\n",
    "            text = raw_input('Entre com a linha/coluna: ')\n",
    "            if len(text) != 2:\n",
    "                continue\n",
    "            si = text[0]\n",
    "            sj = text[1]\n",
    "            i = int(si)-1\n",
    "            j = int(sj)-1\n",
    "            idx = 3*i+j\n",
    "            if board[idx] == empty:\n",
    "                break\n",
    "        return idx\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SDMPlayer(object):\n",
    "    def __init__(self, name, sdm0, sdm1, bs_to_boards, boards_to_bs):\n",
    "        self.name = name\n",
    "\n",
    "        self.bs_to_boards = bs_to_boards\n",
    "        self.boards_to_bs = boards_to_bs\n",
    "        \n",
    "        self.sdm0 = sdm0\n",
    "        self.sdm1 = sdm1\n",
    "        \n",
    "        self.on_finish = self.on_finish1\n",
    "        \n",
    "        self.debug = False\n",
    "        \n",
    "        self.reset_stats()\n",
    "    \n",
    "    def reset_stats(self):\n",
    "        self.stats = defaultdict(int)\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def board_to_bitstring(self, board):\n",
    "        bs = self.boards_to_bs.get(board, None)\n",
    "        if bs is None:\n",
    "            bs = sdmlib.Bitstring.init_random(self.sdm0.bits)\n",
    "            self.bs_to_boards[bs] = board\n",
    "            self.boards_to_bs[board] = bs\n",
    "            self.sdm0.write(bs, bs)\n",
    "        return bs\n",
    "    \n",
    "    def bitstring_to_board(self, bs):\n",
    "        board = self.bs_to_boards.get(bs, None)\n",
    "        return board\n",
    "    \n",
    "    def flip(self, board):\n",
    "        return board.translate(flip_table)\n",
    "    \n",
    "    def penalize(self, board, ignore, weight=2):\n",
    "        debug = False\n",
    "        if debug:\n",
    "            print('board')\n",
    "            print_board(board)\n",
    "            print('ignore')\n",
    "            print_board(ignore)\n",
    "        for idx, x in enumerate(board):\n",
    "            if x == empty:\n",
    "                board2 = board[:idx] + 'X' + board[idx+1:]\n",
    "                if board2 != ignore:\n",
    "                    if debug:\n",
    "                        print('aceito')\n",
    "                        print_board(board2)\n",
    "                    bs1 = self.board_to_bitstring(board)\n",
    "                    bs2 = self.board_to_bitstring(board2)\n",
    "                    self.sdm1.write(bs1, bs2, weight=weight)\n",
    "                else:\n",
    "                    if debug:\n",
    "                        print('Ignorou!')\n",
    "                        print_board(board2)\n",
    "        \n",
    "    \n",
    "    def on_finish1(self, winner, seq):\n",
    "        prev = None\n",
    "        if self.debug:\n",
    "            print('Learning...')\n",
    "            \n",
    "        #if winner is not None:\n",
    "        #    if winner == 'X':\n",
    "        #        self.penalize(self.flip(seq[-3]), self.flip(seq[-2]))\n",
    "        #    else:\n",
    "        #        self.penalize(seq[-3], seq[-2])\n",
    "            \n",
    "        for step, board in enumerate(seq):\n",
    "            if winner == 'O':\n",
    "                # Internally SDM is always the X player.\n",
    "                board = self.flip(board)\n",
    "\n",
    "            if prev is not None:\n",
    "                if self.debug:\n",
    "                    print_board(prev)\n",
    "                    print('')\n",
    "                    print_board(board)\n",
    "                bs1 = self.board_to_bitstring(prev)\n",
    "                bs2 = self.board_to_bitstring(board)\n",
    "                if winner is None:\n",
    "                    if self.debug:\n",
    "                        print('It is a draw, so writing with weight=1...')\n",
    "                    self.sdm1.write(bs1, bs2)\n",
    "                else:\n",
    "                    # weight goes from 2 to 5\n",
    "                    weight = int(3.0*step/(len(seq)-1))+2\n",
    "                    if self.debug:\n",
    "                        print('It is a win, so writing with weight={}...'.format(weight))\n",
    "                    self.sdm1.write(bs1, bs2, weight=weight)\n",
    "                \n",
    "                # Penalty\n",
    "                if step%2 == len(seq)%2:\n",
    "                    if self.debug: \n",
    "                        print('Penalizing the flipping board...')\n",
    "                    self.penalize(self.flip(prev), self.flip(board))\n",
    "\n",
    "            prev = board\n",
    "                \n",
    "    def next_move(self, step, board, debug=False):\n",
    "        x = self.sdm_move(step, board)\n",
    "        if x is not None:\n",
    "            if debug:\n",
    "                print('SDM')\n",
    "            return x\n",
    "        if debug:\n",
    "            print('Random')\n",
    "        return self.random_move(step, board)\n",
    "    \n",
    "    def sdm_move(self, step, board, debug=False):\n",
    "        if self.name == 'O':\n",
    "            # Internally SDM is always the X player.\n",
    "            board = self.flip(board)\n",
    "\n",
    "        bs1 = self.board_to_bitstring(board)\n",
    "        bs2 = self.sdm1.read(bs1)\n",
    "        bs3 = self.sdm0.iter_read(bs2, max_iter=10)\n",
    "        board2 = self.bitstring_to_board(bs3)\n",
    "        if board2 is None:\n",
    "            return None\n",
    "        \n",
    "        if debug:\n",
    "            print('Reading...')\n",
    "            print_board(board)\n",
    "            print('--')\n",
    "            print_board(board2)\n",
    "            print('')\n",
    "        \n",
    "        diff = []\n",
    "        for idx in range(9):\n",
    "            if board[idx] != board2[idx]:\n",
    "                if board[idx] == empty and board2[idx] == 'X':\n",
    "                    diff.append(idx)\n",
    "        if len(diff) == 1:\n",
    "            self.stats['sdm'] += 1\n",
    "            return diff[0]\n",
    "        self.stats['weird'] += 1\n",
    "        return None\n",
    "    \n",
    "    def random_move(self, step, board):\n",
    "        self.stats['random'] += 1\n",
    "        v = []\n",
    "        for idx, x in enumerate(board):\n",
    "            if x == empty:\n",
    "                v.append(idx)\n",
    "        return random.choice(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_board(board):\n",
    "    i = 0\n",
    "    while i < len(board):\n",
    "        print('|' + board[i:i+3] + '|')\n",
    "        i += 3\n",
    "    print('')\n",
    "\n",
    "def check_all_equal(*args):\n",
    "    if len(set(args)) == 1 and args[0] != empty:\n",
    "        return True\n",
    "    return False\n",
    "        \n",
    "def check_for_winner(board):\n",
    "    for i in range(3):\n",
    "        if check_all_equal(board[3*i+0], board[3*i+1], board[3*i+2]):\n",
    "            return board[3*i+0]\n",
    "        \n",
    "        if check_all_equal(board[3*0+i], board[3*1+i], board[3*2+i]):\n",
    "            return board[3*0+i]\n",
    "\n",
    "    if check_all_equal(board[3*0+0], board[3*1+1], board[3*2+2]):\n",
    "        return board[3*0+0]\n",
    "    \n",
    "    if check_all_equal(board[3*0+2], board[3*1+1], board[3*2+0]):\n",
    "        return board[3*0+2]\n",
    "    \n",
    "    return None\n",
    "\n",
    "def play(p1, p2, shuffle=True):\n",
    "    board = ' '*9\n",
    "    end = False\n",
    "    players = [p1, p2]\n",
    "    if shuffle:\n",
    "        random.shuffle(players)\n",
    "    index = 0\n",
    "    step = 0\n",
    "    sequence = [board]\n",
    "    winner = None\n",
    "    while winner is None and step < 9:\n",
    "        cur_player = players[index]\n",
    "        \n",
    "        idx = cur_player.next_move(step, board)\n",
    "        \n",
    "        if board[idx] != empty:\n",
    "            cur_player.on_invalid_move()\n",
    "        \n",
    "        board = board[:idx] + cur_player.name + board[idx+1:]\n",
    "        sequence.append(board)\n",
    "        \n",
    "        winner = check_for_winner(board)\n",
    "        \n",
    "        index = (index+1)%2\n",
    "        step += 1\n",
    "    return winner, sequence"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "def run(pA, pB, n, show=False, debug=False, shuffle=True, learning=True, offset=0):\n",
    "    wins = defaultdict(int)\n",
    "    for i in range(n):\n",
    "        winner, seq = play(pA, pB, shuffle=shuffle)\n",
    "\n",
    "        wins[winner] += 1\n",
    "\n",
    "        clear_output(wait=True)\n",
    "        print('Game #{:5d}: {}  {} {}'.format(i+1+offset, list(wins.items()), list(pA.stats.items()), list(pB.stats.items())))\n",
    "\n",
    "        if debug:\n",
    "            for i, board in enumerate(seq):\n",
    "                print('step={}'.format(i))\n",
    "                print_board(board)\n",
    "                \n",
    "        if show:\n",
    "            print('')\n",
    "            print_board(seq[-1])\n",
    "            print('')\n",
    "\n",
    "        if learning:\n",
    "            pA.on_finish(winner, seq)\n",
    "            pB.on_finish(winner, seq)\n",
    "    return wins"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "bits, radius = 1000, 451\n",
    "#bits, radius = 256, 103\n",
    "\n",
    "sample = 1000000\n",
    "scanner_type = sdmlib.SDM_SCANNER_OPENCL\n",
    "\n",
    "address_space = sdmlib.AddressSpace.init_random(bits, sample)\n",
    "\n",
    "counter0 = sdmlib.Counter.init_zero(bits, sample)\n",
    "sdm0 = sdmlib.SDM(address_space, counter0, radius, scanner_type)\n",
    "\n",
    "counter1 = sdmlib.Counter.init_zero(bits, sample)\n",
    "sdm11 = sdmlib.SDM(address_space, counter1, radius, scanner_type)\n",
    "\n",
    "#counter2 = sdmlib.Counter.init_zero(bits, sample)\n",
    "#sdm12 = sdmlib.SDM(address_space, counter2, radius, scanner_type)\n",
    "\n",
    "bs_to_boards = {}\n",
    "boards_to_bs = {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "scanner_type = sdmlib.SDM_SCANNER_OPENCL\n",
    "sdm0 = sdmlib.SDM(address_space, counter0, radius, scanner_type)\n",
    "sdm11 = sdmlib.SDM(address_space, counter1, radius, scanner_type)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "sdmX = SDMPlayer('X', sdm0, sdm11, bs_to_boards, boards_to_bs)\n",
    "#sdmO = SDMPlayer('O', sdm0, sdm12, bs_to_boards, boards_to_bs)\n",
    "rndX = Player('X')\n",
    "rndO = Player('O')\n",
    "humO = HumanPlayer('O')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "humX = HumanPlayer('X')\n",
    "smtO = SmartPlayer('O')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "resultsSmart = []\n",
    "resultsRandom = []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(10):\n",
    "    %time resultsSmart.append(run(sdmX, smtO, 100, show=True, offset=100*i))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Game # 1833: [('X', 15), (None, 4), ('O', 14)]  [('sdm', 4035), ('weird', 55), ('random', 6582)] []\n",
      "\n",
      "|OXO|\n",
      "|OXX|\n",
      "|XOO|\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for i in range(30):\n",
    "    %time resultsRandom.append(run(sdmX, rndO, 100, show=True, offset=100*i))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#%time resultsSmart.append(run(sdmX, smtO, 200, show=True, learning=False))\n",
    "#%time resultsRandom.append(run(sdmX, rndO, 200, show=True, learning=False))\n",
    "\n",
    "for i in range(10):\n",
    "    %time resultsSmart.append(run(sdmX, smtO, 200, show=True, learning=True))\n",
    "    %time resultsRandom.append(run(sdmX, rndO, 200, show=True, learning=False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%time run(sdmX, sdmO, 1000, show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%time run(sdmX, rndO, 1000, show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%time run(rndX, smtO, 100, show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sdmX.debug = False\n",
    "run(sdmX, humO, 1, show=True, debug=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# counter11 = sdmlib.Counter.init_zero(bits, sample)\n",
    "sdm13 = sdmlib.SDM(address_space, counter11, radius, scanner_type)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "playerX = SDMPlayer('O', sdm0, sdm13, bs_to_boards, boards_to_bs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%time run(sdmX, playerX, 1000, show=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%time run(humX, smtO, 1, show=True, debug=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sdmX.next_move(0, 'O X O    ', debug=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sdmX.penalize('O X O    ', 'O X OX   ', weight=1000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(resultsSmart), len(resultsRandom)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x118208a50>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def draw(results):\n",
    "    import numpy as np\n",
    "\n",
    "    pos = np.arange(len(results))\n",
    "    width = 0.35\n",
    "\n",
    "    plt.figure(figsize=(8, 6), dpi=100)\n",
    "    winX = [100.0*x['X']/(x['X'] + x['O'] + x[None]) for x in results]\n",
    "    draw = [100.0*x[None]/(x['X'] + x['O'] + x[None]) for x in results]\n",
    "    winO = [100.0*x['O']/(x['X'] + x['O'] + x[None]) for x in results]\n",
    "    plt.bar(pos, winX, width, color='r')\n",
    "    plt.bar(pos, draw, width, bottom=winX, color='b')\n",
    "    #plt.bar(pos, winO, width)\n",
    "    #plt.bar(pos+width, [100.0*x['X']/(x['X'] + x['O'] + x[None]) for x in resultsRandom], width, color='y')\n",
    "\n",
    "    plt.ylabel('% of wins')\n",
    "    plt.xlabel('Number of games (hundreds)');\n",
    "\n",
    "draw(resultsRandom)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[42, 51, 55, 59, 57, 58, 46, 60, 60, 56]"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "resultsRandom = resultsSmart\n",
    "[x['X'] for x in resultsRandom]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = resultsSmart[0]\n",
    "x.get(u'X')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
